/* 
 * File:   ConnectionManager.hpp
 * Author: Faisal
 *
 * Created on April 27, 2012, 8:39 PM
 *
 * Description:
 *		This is the class that handles the connection pooling for the application. It is internally used by the differnet "Data Context" classes.
 */

#ifndef HConnectionManager_DL

#define HConnectionManager_DL

#include "../Include/Shared.hpp"
#include "../Include/ConnectionDefinition.hpp"
#include "../Include/DLContract.hpp"
#include "MySqlDC.hpp"
#include <Common/Include/ConnectionSourceName.hpp>


#include <queue>
#include <map>
#include <mutex>

using Supetronix::PersistO::Common::ConnectionSourceName;
using Supetronix::PersistO::DL::ConnectionDefinition;
using Supetronix::PersistO::DL::DLContract;

using std::string;
using std::queue;
using std::map;
using std::mutex;


namespace Supetronix
{
	namespace PersistO
	{
		namespace DL
		{
			//definition
			template<class conn>
			class ConnectionManager
			{
			public:
				/*
				*	Methods
				*/

				//get the connection from the connection pool for the specific connection source and connection type
				static const conn* AcquireConnection(ConnectionSourceName sourceName, DLContract* dataContext);

				//once the connection is used to get the data then return it back to pool
				static void ReleaseConnection(conn* connection);

			private:
				/*
				* Member variables
				*/

				//internal cache to store the connections
				static map<ConnectionSourceName, queue<conn*>*>* _connectionPool; 

				//keeping the count of connections already acquired by the application. It is different from the available connections in the pool. This will be the total connections
				//that applciation has opened
                static map<ConnectionSourceName, int>* _connectionCount;

				//this mutex will be used to synchronize the push and pop from the connections queue.
				static std::mutex queueLock;
			};
                        
            template<class conn>
            map<ConnectionSourceName, queue<conn*>*>* ConnectionManager<conn>::_connectionPool = new map<ConnectionSourceName, queue<conn*>*>();
                        
            template<class conn>
            map<ConnectionSourceName, int>* ConnectionManager<conn>::_connectionCount = new map<ConnectionSourceName, int>();


			/*
			*
			*	** Implementation **
			*
			*	This implementation need to be moved to a separate file but method implementation need to remain with the template definition
			*
			*/

			//Get the connection from the connection pool, if an open connection is available otherwise create the connection for the specific provider based on the "DataContext" and the source name.
			//If maximum connection pool size is reached then wait on the queue for the specific interval otherwise throw the exception
			template<class conn>
			const conn* ConnectionManager<conn>::AcquireConnection(ConnectionSourceName sourceName, DLContract* dataContext)
			{   
				//this is to synchronize the multithreaded application for reading the data from the queue
                static std::mutex connectionLock;
				conn* sqlConn;

                //get the connection definition
                const ConnectionDefinition* definition = ConnectionDefinition::Get(sourceName);
                                                      
				//a reference to connection pool
                queue<conn*>* connectionList = nullptr;

                //find the connection from the pool for the specific connection source
				auto connPoolItr = _connectionPool->find(sourceName);

				//if iterator points to queue end then queue is empty and we need to create new connection otherwise get the queue and store it in "connectionList"
				if ( connPoolItr != _connectionPool->end() )
					connectionList = connPoolItr->second;
                            
                try
                {
					//critical section
                    connectionLock.lock();
                                
                    int currentCount = 0;

					//get the total count of opened connections for the specific connection source
					auto connCountItr = _connectionCount->find(sourceName);

					//if connectionCount cache is empty then this is the first connection
					if ( connCountItr != _connectionCount->end() )
						currentCount = connCountItr->second;
                                
                    // if queue is not empty then get the first available connection and remove it from the queue
                    if ( connectionList && !connectionList->empty() )
                    {  
						//read the first element from the queue
                        sqlConn = connectionList->front();

						//pop the item from the queue which we just retrieved
                        connectionList->pop();
                    }
                    else if ( currentCount < definition->MaxPoolSize() )
                    {
                        //if queue is empty then check how many connections are allowed for the specific source
                        //increment the total connection counter which we will need to save back to connection count list
						currentCount++;

						//if not at the end of connection list then update the last value with the new count
						if ( connCountItr != _connectionCount->end() )
							connCountItr->second = currentCount;
						else
						{
							//connection count queue is empty then insert the count for the specific source name
							_connectionCount->insert(std::make_pair(sourceName, currentCount));
						}
                        
						//create the connection and return it
                        sqlConn = (conn*) dataContext->createConnection(definition);
                    }
                }
                catch (...)
                {
					//if there is any exception then unlock the mutex and throw the exception again
                    connectionLock.unlock();
                    throw;
                }

				//unlock the mutex if everything is good
                connectionLock.unlock();

				//if we were able to pull a connection from cache then return it back
				if ( sqlConn )
					return sqlConn;
                
				/*
				*	TODO part
				*/
                //if still unable to get connection from pool then wait on the pool until it times out or connection is available
				//use the "queueLock" mutex to lock again and then call the wait on the mutex so we can created a blocked queue and wait for n number of seconds
				//ReleaseConnection will notify the mutex to read the data from the mutex
                            
                            
                //_connectionPool->insert(std::make_pair(sourceName, c));
                return nullptr;
			}

			template<class conn>
			void ConnectionManager<conn>::ReleaseConnection(conn* connection)
			{
			}

		}
	}
}

#endif